1 理论分析
1.1 DHT11介绍
DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。
DHT11传感器包括一个电阻式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个 DHT11 传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式储存在 OTP 内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达 20 米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选则。产品为 4 针单排引脚封装。连接方便,特殊封装形式可根据用户需求而提供。

DHT11 的技术参数如下:
工作电压范围: 3.3V-5.5V
工作电流 :平均 0.5mA
输出:单总线数字信号
测量范围:湿度 20~90%RH,温度 0~50℃
精度 :湿度±5%,温度±2℃
分辨率 :湿度 1%,温度 1℃
1.2 GD32读取 DHT11
DHT11模块的数据管脚用于 MCU与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间 4ms 左右,一次完整的数据传输为 40bit,高位先出,数据分小数部分和整数部分,具体格式是:
8bit 湿度整数数据+8bit 湿度小数数据+8bi 温度整数数据+8bit 温度小数数据 +8bit校验和数据传送正确时校验和数据等于 8bit 湿度整数数据+8bit 湿度小数数据+8bi 温度整数数据+8bit 温度小数数据所得结果的末 8 位。
传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。例如,某次从 DHT11 读到的数据如下所示。

由以上数据就可得到湿度和温度的值,计算方法:
湿度= byte4 . byte3=45.0 (%RH)
温度= byte2 . byte1=28.0 ( ℃)
校验= byte4+ byte3+ byte2+ byte1=73(=湿度+温度)(校验正确)
可以看出, DHT11 的数据格式是十分简单的, DHT11 和 MCU 的一次通信最大为 3ms 左右,建议主机连续读取时间间隔不要小于 100ms。
下面,介绍一下 DHT11 的传输时序。
1.主机与DHT11通讯流程
主机MCU 发送一次开始信号后,DHT11 从低功耗模式转换到高速模式,等待主机开始信号结束后,即:拉低数据线,保持至少 18ms时间,然后拉高数据线 20~40us时间。DHT11 发送响应信号,送出 40bit 的数据,并触发一次信号采集,用户可选择读取部分数据。正常情况, DHT11 会拉低数据线,保持80us时间,作为响应信号,然后 DHT11 拉高数据线,保持80us时间后,开始输出数据。DHT11 接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11 不会主动进行温湿度采集。采集数据后转换到低速模式。

2.主机复位信号和 DHT11 响应信号
总线空闲状态为高电平,主机把总线拉低等待 DHT11 响应,主机把总线拉低必须大于 18 毫秒,保证 DHT11 能检测到起始信号。DHT11 接收到主机的开始信号后,等待主机开始信号结束,然后发送 80us 低电平响应信号。主机发送开始信号结束,延时等待 80us 后,读取 DHT11 的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可,总线由上拉电阻拉高,如图中黑色线所示。

总线为低电平,说明 DHT11 发送响应信号,DHT11 发送响应信号后,再把总线拉高 80us,准备发送数据,每一 bit 数据都以 50us 低电平时隙开始,高电平的长短定了数据位是 0 还是 1。格式如图中灰色线所示。如果读取响应信号为高电平,则 DHT11 没有响应,请检查线路是否连接正常。当最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,随后总线由上拉电阻拉高进入空闲状态。
3.数字‘ 0’信号表示方法
数字 0 信号表示方法如图所示

4.数字‘ 1’信号表示方法
数字 1 信号表示方法如图所示

这里我们可以看出,数字“0”和数字“1”不同的地方在于高电平的时间不同,这也是读取数据的关键所在。
2 实验详解
2.1 实验目的
1) 通过实验掌握GD32 芯片GPIO 的配置方法
2) 掌握温湿度传感器DHT11 的原理与使用
2.2 实验设备
硬件:PC 机一台;GD32开发板一套; DHT11 一个
软件:Windows 10系统,Keil5集成开发环境、串口助手
2.3 实验相关电路图
DHT11模块相关电路途如下图所示:

传感器模块是单总线通信,单总线通常要求外接一个4.7kΩ的上拉电阻,这样,当总线闲置时,总线上始终是高电平
【注】VCC接开发板的5V、GND 接开发板的GND、DATA接到开发板的PF3上,当然,也可以接到其他引脚上,修改相应的GPIO代码即可。
2.4 DHT11数据读取
代码很简单。主要是根据DHT11的操作即可,另外需要高精度延时,这里用到了滴答定时器,这个在前面的章节以已经讲过了,这里就不在细说了。接下来看看DHT11的数据采集实现。
1.首先对GPIO初始化,配置为推挽输出,速度设置为高速。
/**
* @brief 配置DHT11用到的IO口
* @param None
* @retval None
*/
static void dht11_gpio_config ( void )
{
/* enable the clock */
rcu_periph_clock_enable(DHT11_DOUT_GPIO_CLK);
/* configure GPIO port */
gpio_init(DHT11_DOUT_GPIO_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, DHT11_DOUT_GPIO_PIN);
}
2.接着就是主机MCU发送一次开始信号,主机把总线拉低必须大于 18 毫秒,保证 DHT11 能检测到起始信号,然后拉高数据线 20~40us时间。
/*输出模式*/
dht11_mode_out_pp;
/*主机拉低*/
DHT11_Dout_0;
/*延时18ms*/
delay_ms(18);
/*总线拉高 主机延时30us*/
DHT11_Dout_1;
dht11_delay_us (30); //延时30us
3.DHT11 接收到主机的开始信号后,等待主机开始信号结束,然后发送 80us 低电平响应信号。主机发送开始信号结束,延时等待 80us 后, DHT11 开始输出数据。每一 bit 数据都以 50us 低电平时隙开始,高电平的长短定了数据位是 0 还是 1。
DHT11读取一个字节数据的函数如下。
/**
* @brief 从DHT11读取一个字节,MSB先行
* @param None
* @retval byte
*/
static uint8_t dht11_read_byte ( void )
{
uint8_t i, byte=0;
for(i=0;i<8;i++)
{
/*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/
while(DHT11_DOUT_IN()==RESET);
/*DHT11 以26~28us的高电平表示“0”,以70us高电平表示“1”,
*通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时
*/
dht11_delay_us(40); //延时x us 这个延时需要大于数据0持续的时间即可
if(DHT11_DOUT_IN()==SET)/* x us后仍为高电平表示数据“1” */
{
/* 等待数据1的高电平结束 */
while(DHT11_DOUT_IN()==SET);
byte|=(uint8_t)(0x01<<(7-i)); //把第7-i位置1,MSB先行
}
else // x us后为低电平表示数据“0”
{
byte&=(uint8_t)~(0x01<<(7-i)); //把第7-i位置0,MSB先行
}
}
return byte;
}
这个函数就是来读取DHT11的数据,DHT11通过不同的延时来表示“0”和“1”,以26~28us的高电平表示“0”,以40us高电平表示“1”,因此这里通过去一个中间延时来区分。
4.主机MCU读取完40位数据后,需要检查数据的正确性。
下面给出一次温湿度数据的读取代码。
/**
* @brief 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
* @param DHT11_Data:DHT11数据类型
* @retval ERROR: 读取出错
* SUCCESS:读取成功
*/
uint8_t dht11_read_temp_humidity(dht11_data_t *dht11_data)
{
/*输出模式*/
dht11_mode_out_pp();
/*主机拉低*/
DHT11_DOUT_0;
/*延时18ms*/
delay_ms(18);
/*总线拉高 主机延时30us*/
DHT11_DOUT_1;
dht11_delay_us(30); //延时30us
/*主机设为输入 判断从机响应信号*/
dht11_mode_ipu();
/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
if(DHT11_DOUT_IN()==RESET)
{
/*轮询直到从机发出 的80us 低电平 响应信号结束*/
while(DHT11_DOUT_IN()==RESET);
/*轮询直到从机发出的 80us 高电平 标置信号结束*/
while(DHT11_DOUT_IN()==SET);
/*开始接收数据*/
dht11_data->humi_int = dht11_read_byte();
dht11_data->humi_deci = dht11_read_byte();
dht11_data->temp_int = dht11_read_byte();
dht11_data->temp_deci = dht11_read_byte();
dht11_data->check_sum = dht11_read_byte();
/*读取结束,引脚改为输出模式*/
dht11_mode_out_pp();
/*主机拉高*/
DHT11_DOUT_1;
/*检查读取的数据是否正确*/
if(dht11_data->check_sum==dht11_data->humi_int+dht11_data->humi_deci+dht11_data->temp_int+dht11_data->temp_deci)
{
return SUCCESS;
}
else
{
return ERROR;
}
}
else
{
return ERROR;
}
}
这个完整流程请参看前问的时序。
主函数代码如下:
/*
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
st_bsp_usart_dev bsp_usart_dev0 = USART_DEV0_CONFIG;
st_bsp_led_dev bsp_led_dev0 = LED_DEV0_CONFIG;
//systick init
sysTick_init();
// LED1 init
bsp_led_init(&bsp_led_dev0);
//usart init 115200 8-N-1
bsp_usart_init(&bsp_usart_dev0, USART_MODE_EXTI, 115200, 0, 1);
// DMA config
bsp_usart_dma_init();
/* USART DMA 发送使能 */
usart_dma_enable(USART0, USART_DMA_TRANSMIT);
/* USART DMA接收使能 */
usart_dma_enable(USART0, USART_DMA_RECEIVE);
printf("DHT11 temperature and humidity sensor data reading\r\n");
/*初始化DTT11的引脚*/
dht11_init();
while(1)
{
/*调用DHT11_Read_TempAndHumidity读取温湿度,若成功则输出该信息*/
if(dht11_read_temp_humidity(&dht11_data)==SUCCESS)
{
printf("humidity : %d.%d RH , temperature : %d.%d C\r\n",
dht11_data.humi_int,dht11_data.humi_deci,
dht11_data.temp_int,dht11_data.temp_deci);
}
else
{
printf("Failed to read DHT11 information\r\n");
}
bsp_led_toggle(&bsp_led_dev0);
delay_ms(1000);
}
}
2.5 实验现象
接上DHT11模块,每隔1s就输出温湿度。

欢迎访问我的网站
BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎
欢迎订阅我的微信公众号
关注公众号[嵌入式实验楼]获取更多资讯
欢迎订阅我的知识星球
关注知识星球[嵌入式实验楼]获取更多资讯
资源获取方式
1.关注公众号[嵌入式实验楼]
2.在公众号回复关键词[GD32开发实战指南]获取资料提取码